// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using SnapObjects.Commons;

namespace SnapObjects.Expressions
{
    public static class Extensions
    {
        public static bool IsImplicitFrom(this Type toType, Type fromType)
        {
            if (toType.IsNullable())
            {
                toType = toType.GenericTypeArguments[0];
            }

            if (fromType.IsNullable())
            {
                fromType = fromType.GenericTypeArguments[0];
            }

            if ((fromType.IsPrimitive || fromType == typeof(decimal)) &&
                (toType.IsPrimitive || toType == typeof(decimal)))
            {
                var fcode = Type.GetTypeCode(fromType);
                var tcode = Type.GetTypeCode(toType);

                if (fcode == tcode)
                {
                    return true;
                }

                switch (fcode)
                {
                    case TypeCode.Char:
                    {
                        switch (tcode)
                        {
                            case TypeCode.UInt16:
                            case TypeCode.UInt32:
                            case TypeCode.Int32:
                            case TypeCode.UInt64:
                            case TypeCode.Int64:
                            case TypeCode.Single:
                            case TypeCode.Double:
                            case TypeCode.Decimal: return true;
                            default: return false;
                        }
                    }
                    case TypeCode.SByte:
                    {
                        switch (tcode)
                        {
                            case TypeCode.Int16:
                            case TypeCode.Int32:
                            case TypeCode.Int64:
                            case TypeCode.Single:
                            case TypeCode.Double:
                            case TypeCode.Decimal: return true;
                            default: return false;
                        }
                    }
                    case TypeCode.Byte:
                    {
                        switch (tcode)
                        {
                            case TypeCode.Char:
                            case TypeCode.UInt16:
                            case TypeCode.Int16:
                            case TypeCode.UInt32:
                            case TypeCode.Int32:
                            case TypeCode.UInt64:
                            case TypeCode.Int64:
                            case TypeCode.Single:
                            case TypeCode.Double:
                            case TypeCode.Decimal: return true;
                            default: return false;
                        }
                    }
                    case TypeCode.UInt16:
                    {
                        switch (tcode)
                        {
                            case TypeCode.UInt32:
                            case TypeCode.Int32:
                            case TypeCode.UInt64:
                            case TypeCode.Int64:
                            case TypeCode.Single:
                            case TypeCode.Double:
                            case TypeCode.Decimal: return true;
                            default: return false;
                        }
                    }
                    case TypeCode.Int16:
                    {
                        switch (tcode)
                        {
                            case TypeCode.Int32:
                            case TypeCode.Int64:
                            case TypeCode.Single:
                            case TypeCode.Double:
                            case TypeCode.Decimal: return true;
                            default: return false;
                        }
                    }
                    case TypeCode.UInt32:
                    {
                        switch (tcode)
                        {
                            case TypeCode.UInt64:
                            case TypeCode.Int64:
                            case TypeCode.Single:
                            case TypeCode.Double:
                            case TypeCode.Decimal: return true;
                            default: return false;
                        }
                    }
                    case TypeCode.Int32:
                    {
                        switch (tcode)
                        {
                            case TypeCode.Int64:
                            case TypeCode.Single:
                            case TypeCode.Double:
                            case TypeCode.Decimal: return true;
                            default: return false;
                        }
                    }
                    case TypeCode.UInt64:
                    {
                        switch (tcode)
                        {
                            case TypeCode.Single:
                            case TypeCode.Double:
                            case TypeCode.Decimal: return true;
                            default: return false;
                        }
                    }
                    case TypeCode.Int64:
                    {
                        switch (tcode)
                        {
                            case TypeCode.Single:
                            case TypeCode.Double:
                            case TypeCode.Decimal: return true;
                            default: return false;
                        }
                    }
                    case TypeCode.Single:
                    {
                        switch (tcode)
                        {
                            case TypeCode.Double: return true;
                            default: return false;
                        }
                    }
                }
            }
            else
            {
                return toType.IsAssignableFrom(fromType);
            }
            return false;
        }

        public static bool IsInt(this Type type)
        {
            var code = Type.GetTypeCode(type);

            return code >= TypeCode.SByte && code <= TypeCode.UInt64;
        }

        public static bool IsAnyType(this Type type)
        {
            var arr = new Type[]
            {
                typeof(object),
                typeof(object[]),
                typeof(Expression[])
            };

            return arr.Contains(type);
        }

        public static string GetNewStr(this IEnumerable<string> properties)
        {
            properties = properties.Where(x => !String.IsNullOrWhiteSpace(x));

            if (!properties.Any())
            {
                throw new ArgumentException("Invalid parameters.", nameof(properties));
            }

            var strExpr = "";
            if (properties.Count() == 1)
            {
                strExpr = properties.First();
            }
            else
            {
                var index = 0;
                var props = properties.Select(x => $"Prop{ index++ } = {x}");

                strExpr = "new { " + String.Join(",", props) + " }";
            }

            return strExpr;
        }

        public static bool TryAdd(
            this ICollection<ParameterExpression> useds,
            ParameterExpression parameter)
        {
            if (useds.Contains(parameter))
            {
                return false;
            }

            useds.Add(parameter);

            return true;
        }

        public static void TryAddRange(
            this ICollection<ParameterExpression> useds,
            IEnumerable<ParameterExpression> parameters)
        {
            foreach (var parameter in parameters)
            {
                useds.TryAdd(parameter);
            }
        }
    }
}
